#include <VM/value.h>
#include <VM/object.h>
#include <Common/util.h>
#include <stdexcept>
#include <cmath>

const char* TypeName(const Value& val)
{
	switch (val.index())
	{
	case NIL:
		return "nil";

	case BOOL:
		return "bool";

	case NUMBER:
		return "number";

	case STRING:
		return "string";

	case FUNCTION:
		return "function";

	case NATIVE_FUNCTION:
		return "native function";
	}

	return "impossible";
}

std::ostream& operator<<(std::ostream& os, const Value& v)
{
	switch (v.index()) {
	// monostate = nil
	case NIL:
	{
		os << "nil";
		break;
	}
	// bool
	case BOOL:
	{
		os << (std::get<bool>(v) ? "true" : "false");
		break;
	}
	// double
	case NUMBER:
	{
		os << std::get<double>(v);
		break;
	}

	case STRING:
	{
		os << "\"" << std::get<std::string>(v) << "\"";
		break;
	}

	case FUNCTION:
	{
		os << "<function " << std::get<FunctionPtr>(v)->name << ">";
		break;
	}

	case NATIVE_FUNCTION:
	{
		os << "<native function " << std::get<NativeFuncPtr>(v)->name << ">";
		break;
	}

	}

	return os;
}

bool isTrue(const Value& val)
{
	if (std::holds_alternative<bool>(val))
		return std::get<bool>(val);
	else if (std::holds_alternative<double>(val))
		return std::get<double>(val) > 0;

	return false;
}

Value operator+(const Value& lhs, const Value& rhs)
{
	if (std::holds_alternative<double>(lhs) && std::holds_alternative<double>(rhs))
		return std::get<double>(lhs) + std::get<double>(rhs);
	else if (std::holds_alternative<std::string>(lhs) && std::holds_alternative<std::string>(rhs))
		return std::get<std::string>(lhs) + std::get<std::string>(rhs);

	throw std::runtime_error(format("Illegal operator '+' for operands type(%s) and type(%s)", TypeName(lhs),
		TypeName(rhs)));
}

Value operator-(const Value& lhs, const Value& rhs)
{
	if (std::holds_alternative<double>(lhs) && std::holds_alternative<double>(rhs)) {
		return std::get<double>(lhs) - std::get<double>(rhs);
	}

	throw std::runtime_error(format("Illegal operator '-' for operands type(%s) and type(%s)", TypeName(lhs),
		TypeName(rhs)));
}

Value operator*(const Value& lhs, const Value& rhs)
{
	if (std::holds_alternative<double>(lhs) && std::holds_alternative<double>(rhs)) {
		return std::get<double>(lhs) * std::get<double>(rhs);
	}

	throw std::runtime_error(format("Illegal operator '*' for operands type(%s) and type(%s)", TypeName(lhs),
		TypeName(rhs)));
}

Value operator/(const Value& lhs, const Value& rhs)
{
	if (std::holds_alternative<double>(lhs) && std::holds_alternative<double>(rhs)) {
		double right = std::get<double>(rhs);
		if (right == 0)
			throw std::runtime_error("Divided by Zero!");

		return std::get<double>(lhs) / right;
	}

	throw std::runtime_error(format("Illegal operator '/' for operands type(%s) and type(%s)", TypeName(lhs),
		TypeName(rhs)));
}

bool operator<(const Value& lhs, const Value& rhs)
{
	if (std::holds_alternative<double>(lhs) && std::holds_alternative<double>(rhs))
		return std::get<double>(lhs) < std::get<double>(rhs);
	else if (std::holds_alternative<std::string>(lhs) && std::holds_alternative<std::string>(rhs))
		return std::get<std::string>(lhs) < std::get<std::string>(rhs);

	throw std::runtime_error(format("Illegal operator '<' for operands type(%s) and type(%s)", TypeName(lhs),
		TypeName(rhs)));
}

bool operator>(const Value& lhs, const Value& rhs)
{
	if (std::holds_alternative<double>(lhs) && std::holds_alternative<double>(rhs))
		return std::get<double>(lhs) > std::get<double>(rhs);
	else if (std::holds_alternative<std::string>(lhs) && std::holds_alternative<std::string>(rhs))
		return std::get<std::string>(lhs) > std::get<std::string>(rhs);

	throw std::runtime_error(format("Illegal operator '>' for operands type(%s) and type(%s)", TypeName(lhs),
		TypeName(rhs)));
}

bool operator==(const Value& lhs, const Value& rhs)
{
	if (lhs.index() != rhs.index())
		return false;

	switch (lhs.index())
	{
	case NIL:
		return true;

	case BOOL:
		return std::get<bool>(lhs) == std::get<bool>(rhs);

	case NUMBER:
		return std::get<double>(lhs) == std::get<double>(rhs);

	case STRING:
		return std::get<std::string>(lhs) == std::get<std::string>(rhs);

	case FUNCTION:
		return std::get<FunctionPtr>(lhs) == std::get<FunctionPtr>(rhs);

	case NATIVE_FUNCTION:
		return std::get<NativeFuncPtr>(lhs) == std::get<NativeFuncPtr>(rhs);
	}

	return false;
}

bool operator!=(const Value& lhs, const Value& rhs)
{
	return !(lhs == rhs);
}

bool operator<=(const Value& lhs, const Value& rhs)
{
	if (std::holds_alternative<double>(lhs) && std::holds_alternative<double>(rhs))
		return std::get<double>(lhs) <= std::get<double>(rhs);
	else if (std::holds_alternative<std::string>(lhs) && std::holds_alternative<std::string>(rhs))
		return std::get<std::string>(lhs) <= std::get<std::string>(rhs);

	throw std::runtime_error(format("Illegal operator '<=' for operands type(%s) and type(%s)", TypeName(lhs),
		TypeName(rhs)));
}

bool operator>=(const Value& lhs, const Value& rhs)
{
	if (std::holds_alternative<double>(lhs) && std::holds_alternative<double>(rhs))
		return std::get<double>(lhs) >= std::get<double>(rhs);
	else if (std::holds_alternative<std::string>(lhs) && std::holds_alternative<std::string>(rhs))
		return std::get<std::string>(lhs) >= std::get<std::string>(rhs);

	throw std::runtime_error(format("Illegal operator '>=' for operands type(%s) and type(%s)", TypeName(lhs),
		TypeName(rhs)));
}

bool isCallable(const Value& val)
{
	if (std::holds_alternative<FunctionPtr>(val) || std::holds_alternative<NativeFuncPtr>(val))
		return true;

	return false;
}
